/*
 * Copyright (c) 2003-2022, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @author Erik Wistrand
 * @author Philippe Laporte
 */

package org.knopflerfish.util.metatype;

import java.lang.reflect.Array;
import java.util.Dictionary;
import java.util.Vector;

import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.ObjectClassDefinition;

import org.knopflerfish.util.Text;

/**
 * Implementation class for {@link AttributeDefinition}.
 *
 * <p>
 * This class contains get and parse methods for operations related to
 * constructing AttributeDefinition.
 * </p>
 */
public class AD
  implements AttributeDefinition, Comparable<AD>, Cloneable
{
  int type;
  int card;
  String[] defValue;
  String desc;
  String id;
  String name;
  String[] optLabels;
  String[] optValues;
  String min;
  String max;

  boolean bOptional = false;
  boolean required;

  /**
   * String used for separating array and vector string representations.
   */
  static final String SEQUENCE_SEP = ",";

  /**
   * Create an AttributeDefinition with empty descrition and no option labels or
   * option values.
   */
  public AD(String id, int type, int card, String name, String[] defValue)
  {
    this(id, type, card, name, "", // desc
         defValue, // defValue
         null, // optLabels
         null // optValues
    );
  }

  /**
   * Create a new attribute definition.
   *
   * @param id
   *          Unique id of the definition
   * @param card
   *          cardinality of the definition
   * @param type
   *          One of the type constants <tt>STRING...BOOLEAN</tt>
   * @throws IllegalArgumentException
   *           if <i>type</i> is not supported.
   * @throws IllegalArgumentException
   *           if <i>id</i> is <tt>null</tt> or empty
   * @throws IllegalArgumentException
   *           if <i>desc</i> is <tt>null</tt>
   */
  public AD(String id, int type, int card, String name, String desc,
            String[] defValue, String[] optLabels, String[] optValues)
  {
    if (type < STRING || type > PASSWORD) {
      throw new IllegalArgumentException("Unsupported type " + type);
    }

    if (id == null || "".equals(id)) {
      throw new IllegalArgumentException("Bad id '" + id + "'");
    }

    if (defValue == null) {
      String s = "";
      switch (type) {
      case STRING:
        s = "";
        break;
      case INTEGER:
      case LONG:
      case SHORT:
      case BYTE:
      case BIGINTEGER:
        s = "0";
        break;
      case BIGDECIMAL:
      case DOUBLE:
      case FLOAT:
        s = "0.0";
        break;
      case CHARACTER:
        s = "-";
        break;
      case BOOLEAN:
        s = "false";
        break;
      case PASSWORD:
        s = "";
        break;
      }
      defValue = new String[] { s };
    }

    this.type = type;
    this.card = card;
    this.desc = desc;
    this.id = id;
    this.name = name;
    setOptions(optValues, optLabels);
    setDefaultValue(defValue);
  }

  public AD(String id, int type, int card, String name, String desc,
            String[] defValue, String min, String max, boolean required)
  {
    this(id, type, card, name, desc, defValue, null, null);
    this.min = min;
    this.max = max;
    this.required = required;
  }

  @Override
  public Object clone()
  {
    final AD newI =
      new AD(id, type, card, name, desc, defValue.clone(), min, max,
             required);
    if (optLabels != null) {
      newI.optLabels = optLabels.clone();
    }
    if (optValues != null) {
      newI.optValues = optValues.clone();
    }
    return newI;
  }

  public int getCardinality()
  {
    return card;
  }

  public String[] getDefaultValue()
  {
    return defValue;
  }

  public void setDescription(String s)
  {
    this.desc = s;
  }

  AD localize(Dictionary<?, ?> dict)
  {
    final AD localized = (AD) this.clone();

    if (name != null && name.startsWith("%")) {
      String sub;
      if ((sub = (String) dict.get(name.substring(1))) != null) {
        localized.name = sub;
      }
    }

    if (desc != null && desc.startsWith("%")) {
      String sub;
      if ((sub = (String) dict.get(desc.substring(1))) != null) {
        localized.desc = sub;
      }
    }

    if (optLabels != null) {
      for (int i = 0; i < optLabels.length; i++) {
        if (optLabels[i].startsWith("%")) {
          String sub;
          if ((sub = (String) dict.get(optLabels[i].substring(1))) != null) {
            localized.optLabels[i] = sub;
          }
        }
      }
    }

    return localized;
  }

  /**
   * Set the default value.
   *
   * @throws IllegalArgumentException
   *           if any of the values cannot be validated.
   */
  public void setDefaultValue(String[] value)
  {
    final String s = validate(toString(value));

    if (s != null && !"".equals(s)) {
      throw new IllegalArgumentException("Bad default value '"
                                         + toString(value) + "' " + ", id="
                                         + id + ", class=" + getClass(type)
                                         + ", err=" + s);
    }

    defValue = value;
  }

  public String getDescription()
  {
    return desc;
  }

  public String getID()
  {
    return id;
  }

  public String getName()
  {
    return name;
  }

  /**
   * Set values returned by <tt>getOptionValues</tt> and
   * <tt>getOptionLabels</tt>.
   *
   * @param optValues
   *          Values to be return by <tt>getOptionValues</tt>. Can be
   *          <tt>null</tt>.
   * @param optLabels
   *          Values to be return by <tt>getOptionLabels</tt>. Can be
   *          <tt>null</tt> iff <tt>optValues</tt> is <tt>null</tt>.
   * @throws IllegalArgumentException
   *           if optValues and optLabels are not the same length.
   */
  public void setOptions(String[] optValues, String[] optLabels)
  {
    if (optValues != null) {
      if (optLabels == null || optValues.length != optLabels.length) {
        throw new IllegalArgumentException(
                                           "Values must be same length as labels");
      }
    }
    if (optValues != null && optValues.length != 0) {
      this.optValues = optValues;
    }
    if (optLabels != null && optLabels.length != 0) {
      this.optLabels = optLabels;
    }
  }

  public String[] getOptionLabels()
  {
    return optLabels;
  }

  public String[] getOptionValues()
  {
    return optValues;
  }

  public int getType()
  {
    return type;
  }

  /**
   * Return true if this attribute is optional.
   */
  public boolean isOptional()
  {
    return bOptional;
  }

  public int getRequired()
  {
    if (required) {
      return ObjectClassDefinition.REQUIRED;
    } else {
      return ObjectClassDefinition.OPTIONAL;
    }
  }

  /**
   * Get the attribute type given any suported java object.
   *
   * @param val
   *          Any java object, including arrays of primitive types. If
   *          <i>val</i> is a Vector, it must contain at least one element.
   * @return <tt>STRING...BOOLEAN</tt>
   * @throws IllegalArgumentException
   *           if type cannot be derived.
   */
  public static int getType(Object val)
  {
    if (val instanceof Vector) {
      final Vector<?> v = (Vector<?>) val;
      if (v.size() == 0) {
        throw new IllegalArgumentException("Vector is empty "
                                           + "-- no type can be derived");
      } else {
        return getType(v.elementAt(0));
      }
    } else if (val.getClass().isArray()) {
      return getArrayType(val);
    } else {
      return getPrimitiveType(val);
    }
  }

  static Class<?> BIGDECIMAL_PRIMITIVE = Double.TYPE;
  static Class<?> BIGDECIMAL_OBJECT = Double.class;

  static Class<?> BIGINTEGER_PRIMITIVE = Integer.TYPE;
  static Class<?> BIGINTEGER_OBJECT = Integer.class;

  static final Class<?>[] ARRAY_CLASSES = new Class[PASSWORD - STRING + 1];
  static final Class<?>[] PRIMITIVE_CLASSES = new Class[] { String.class,
                                                        Long.TYPE,
                                                        Integer.TYPE,
                                                        Short.TYPE,
                                                        Character.TYPE,
                                                        Byte.TYPE, Double.TYPE,
                                                        Float.TYPE,
                                                        BIGINTEGER_PRIMITIVE,
                                                        BIGDECIMAL_PRIMITIVE,
                                                        Boolean.TYPE,
                                                        String.class, };
  static final Class<?>[] OBJECT_CLASSES = new Class[] { String.class, Long.class,
                                                     Integer.class,
                                                     Short.class,
                                                     Character.class,
                                                     Byte.class, Double.class,
                                                     Float.class,
                                                     BIGINTEGER_OBJECT,
                                                     BIGDECIMAL_OBJECT,
                                                     Boolean.class,
                                                     String.class, };

  static {
    try {
      BIGDECIMAL_PRIMITIVE = Class.forName("java.math.BigDecimal");
      BIGDECIMAL_OBJECT = BIGDECIMAL_PRIMITIVE;
    } catch (final Throwable t) {
      // System.out.println("no BigDecimal");
    }
    try {
      BIGINTEGER_PRIMITIVE = Class.forName("java.math.BigInteger");
      BIGINTEGER_OBJECT = BIGINTEGER_PRIMITIVE;
    } catch (final Throwable t) {
      // System.out.println("no BigInteger");
    }

    /*
     * System.out.println("BIGINTEGER_PRIMITIVE=" + BIGINTEGER_PRIMITIVE);
     * System.out.println("BIGINTEGER_OBJECT=" + BIGINTEGER_OBJECT);
     * System.out.println("BIGDECIMAL_PRIMITIVE=" + BIGDECIMAL_PRIMITIVE);
     * System.out.println("BIGDECIMAL_OBJECT=" + BIGDECIMAL_OBJECT);
     */

    try {
      for (int i = STRING; i <= PASSWORD; i++) {
        ARRAY_CLASSES[i - STRING] =
          (Array.newInstance(getPrimitiveClass(i), 0)).getClass();
      }
      /*
       * for(int i = STRING; i <= BOOLEAN; i++) { Object array =
       * Array.newInstance(getPrimitiveClass(i), 0);
       *
       * System.out.println(i + ": " + getPrimitiveClass(i).getName() + ", " +
       * getClass(i).getName() + ", " + array.getClass().getName() + ", " +
       * getArrayType(array));
       *
       * }
       */
    } catch (final Exception e) {
      e.printStackTrace();
      System.exit(0);
    }
  }

  /**
   * Get type from an array object.
   *
   * @param val
   *          an array object
   * @return <tt>STRING...BOOLEAN</tt>
   * @throws IllegalArgumentException
   *           if type cannot be derived.
   */
  public static int getArrayType(Object val)
  {

    // Isn't there an easier way of doing this? Like
    // Array.getElementClass() or similar?
    for (int i = STRING; i <= PASSWORD; i++) {
      if (ARRAY_CLASSES[i - STRING].equals(val.getClass())) {
        return i;
      }
    }
    throw new IllegalArgumentException("Unsupported type "
                                       + val.getClass().getName());
  }

  /**
   * Get type from primitive object.
   *
   * @param val
   *          an object of one of the boxed primitive java object classes.
   * @return <tt>STRING...BOOLEAN</tt>
   * @throws IllegalArgumentException
   *           if type cannot be derived.
   */
  public static int getPrimitiveType(Object val)
  {
    if (val instanceof String) {
      return STRING;
    } else if (val instanceof Integer) {
      return INTEGER;
    } else if (val instanceof Double) {
      return DOUBLE;
    } else if (val instanceof Float) {
      return FLOAT;
    } else if (val instanceof Integer) {
      return INTEGER;
    } else if (val instanceof Long) {
      return LONG;
    } else if (val instanceof Boolean) {
      return BOOLEAN;
    } else if (val instanceof Short) {
      return SHORT;
    } else if (val instanceof Character) {
      return CHARACTER;
    } else if (val instanceof Byte) {
      return BYTE;
    } else if (BIGINTEGER_OBJECT.isAssignableFrom(val.getClass())) {
      return BIGINTEGER;
    } else if (BIGDECIMAL_OBJECT.isAssignableFrom(val.getClass())) {
      return BIGDECIMAL;
    } else {
      throw new IllegalArgumentException("Unsupported type "
                                         + val.getClass().getName());
    }
  }

  /**
   * Implementation of validation function.
   *
   * <p>
   * Validation of primitive types is performed by trying to create an' object
   * from the corresponding String constructor.
   * </p>
   * <p>
   * Validation of arrays and vectors is performed by splitting the input string
   * into comma-separated words.
   * </p>
   */
  public String validate(String value)
  {
    if (value == null) {
      return null;
    }

    if (card == Integer.MIN_VALUE) {
      return validateMany(value, type, Integer.MAX_VALUE);
    } else if (card == Integer.MAX_VALUE) {
      return validateMany(value, type, Integer.MAX_VALUE);
    } else if (card < 0) {
      return validateMany(value, type, -card);
    } else if (card > 0) {
      return validateMany(value, type, card);
    } else {
      return validateSingle(value, type);
    }
  }

  /**
   * Parse a string value to an object given a cardinality and type.
   */
  public static Object parse(String value, int card, int type)
  {
    if (card < 0) {
      return parseMany(value, type);
    } else if (card > 0) {
      final Vector<Object> v = parseMany(value, type);
      final Object array = Array.newInstance(getPrimitiveClass(type), v.size());
      for (int i = 0; i < v.size(); i++) {
        Array.set(array, i, v.elementAt(i));
      }
      return array;
    } else {
      return parseSingle(value, type);
    }
  }

  static Vector<Object> parseMany(String value, int type)
  {
    final String[] items = Text.splitwords(value, SEQUENCE_SEP, '\"');

    // System.out.println("AD.parseMany '" + value + "', item count=" +
    // items.length);
    final Vector<Object> v = new Vector<Object>();
    for (final String item : items) {
      v.addElement(parseSingle(item, type));
    }
    return v;
  }

  String validateMany(String value, int type, int maxItems)
  {
    final String[] items = Text.splitwords(value, SEQUENCE_SEP, '\"');

    if (maxItems == 0) {
      if (items.length != 1) {
        return "Expected one item, found " + items.length;
      }
    }

    if (items.length > maxItems) {
      return "Max # of items are " + maxItems + ", found " + items.length;
    }

    final StringBuilder sb = new StringBuilder();
    for (final String item : items) {
      final String s = validateSingle(item, type);
      if (s != null && !"".equals(s)) {
        if (sb.length() != 0) {
          sb.append(", ");
        }
        sb.append(s);
      }
    }

    return sb.toString();
  }

  String validateSingle(String valueS, int type)
  {
    try {
      switch (type) {
      case STRING:
      case PASSWORD:
        try {
          if (min != null) {
            if (valueS.length() < Integer.parseInt(min)) {
              return "value out of range";
            }
          }
          if (max != null) {
            if (valueS.length() > Integer.parseInt(max)) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (optValues[i].equals(valueS)) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final ClassCastException cce) {

        }
        break;
      case INTEGER:
        final int ivalue = Integer.parseInt(valueS.trim());
        try {
          if (min != null) {
            final int minV = Integer.parseInt(min);
            if (ivalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final int maxV = Integer.parseInt(max);
            if (ivalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Integer.parseInt(optValues[i]) == ivalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {

        }
        break;
      case LONG:
        final long lvalue = Long.parseLong(valueS.trim());
        try {
          if (min != null) {
            final long minV = Long.parseLong(min);
            if (lvalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final long maxV = Long.parseLong(max);
            if (lvalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Long.parseLong(optValues[i]) == lvalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {

        }
        break;
      case BYTE:
        final byte bvalue = Byte.parseByte(valueS.trim());
        try {
          if (min != null) {
            final byte minV = Byte.parseByte(min);
            if (bvalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final byte maxV = Byte.parseByte(max);
            if (bvalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Byte.parseByte(optValues[i]) == bvalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {

        }
        break;
      case SHORT:
        final short svalue = Short.parseShort(valueS.trim());
        try {
          if (min != null) {
            final short minV = Short.parseShort(min);
            if (svalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final short maxV = Short.parseShort(max);
            if (svalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Short.parseShort(optValues[i]) == svalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {

        }
        break;
      case CHARACTER:
        if (valueS.length() != 1) {
          throw new IllegalArgumentException(
                                             "Character strings must be of length 1");
        }
        try {
          if (min != null) {
            if (valueS.compareTo(min) < 0) {
              return "value out of range";
            }
          }
          if (max != null) {
            if (valueS.compareTo(max) > 0) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (optValues[i].equals(valueS)) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final ClassCastException cce) {

        }
        break;
      case DOUBLE:
        final double dvalue = Double.parseDouble(valueS.trim());
        try {
          if (min != null) {
            final double minV = Double.parseDouble(min);
            if (dvalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final double maxV = Double.parseDouble(max);
            if (dvalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Double.parseDouble(optValues[i]) == dvalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {

        }
        break;
      case FLOAT:
        final float fvalue = Float.parseFloat(valueS.trim());
        try {
          if (min != null) {
            final float minV = Float.parseFloat(min);
            if (fvalue < minV) {
              return "value out of range";
            }
          }
          if (max != null) {
            final float maxV = Float.parseFloat(max);
            if (fvalue > maxV) {
              return "value out of range";
            }
          }
          if (optValues != null) {
            int i = 0;
            for (; i < optValues.length; i++) {
              if (Float.parseFloat(optValues[i]) == fvalue) {
                break;
              }
            }
            if (i == optValues.length) {
              return "Value must be one of the options";
            }
          }
        } catch (final NumberFormatException nfe) {
        }
        break;
      case BOOLEAN:
        if (!("true".equals(valueS.trim()) || "false".equals(valueS.trim()))) {
          throw new IllegalArgumentException(
                                             "Booleans must be 'true' or 'false'");
        }
        break;
      default:
        break;
      }
    } catch (final Exception e) {
      // e.printStackTrace();
      return e.getMessage();
    }
    return "";
  }

  /**
   * Parse a single value into an object given a type.
   */
  public static Object parseSingle(String value, int type)
  {

    switch (type) {
    case STRING:
    case PASSWORD:
      return value;
    case INTEGER:
      return new Integer(value.trim());
    case LONG:
      return new Long(value.trim());
    case BYTE:
      return new Byte(value.trim());
    case SHORT:
      return new Short(value.trim());
    case CHARACTER:
      return new Character(value.charAt(0));
    case DOUBLE:
      return new Double(value.trim());
    case FLOAT:
      return new Float(value.trim());
    case BOOLEAN:
      return "true".equals(value.trim()) ? Boolean.TRUE : Boolean.FALSE;
    default:
      throw new IllegalArgumentException("Cannot parse '" + value
                                         + "' to type=" + type);
    }
  }

  /**
   * Get java class corresponding to AttributeDefinition type.
   *
   * @throws IllegalArgumentException
   *           if type is not supporte.d
   */
  public static Class<?> getClass(int type)
  {
    return OBJECT_CLASSES[type - STRING];
  }

  /**
   * Get the primitive java class from a specificed type.
   *
   * @throws IllegalArgumentException
   *           if type is not supported.
   */
  public static Class<?> getPrimitiveClass(int type)
  {
    return PRIMITIVE_CLASSES[type - STRING];
  }

  /**
   * Convert to human-readable string.
   */
  @Override
  public String toString()
  {
    final StringBuilder sb = new StringBuilder();

    sb.append("AD[");

    sb.append("id=").append(id);
    sb.append(", type=").append(type);
    sb.append(", name=").append(name);
    sb.append(", desc=").append(desc);
    sb.append(", cardinality=").append(card);

    sb.append(", defValue=").append(toString(defValue));
    sb.append(", optLabels=").append(toString(optLabels));
    sb.append(", optValues=").append(toString(optValues));

    sb.append("]");

    return sb.toString();
  }

  /**
   * Convert a object to string that can be parsed by <tt>parse</tt>
   */
  public static String toString(Object obj)
  {
    if (obj.getClass().isArray()) {
      return toStringFromArray(obj);
    } else if (obj instanceof Vector) {
      final StringBuilder sb = new StringBuilder();
      final Vector<?> v = (Vector<?>) obj;
      for (int i = 0; i < v.size(); i++) {
        final String s = (String) v.elementAt(i);
        sb.append(escape(s));
        if (i < v.size() - 1) {
          sb.append(SEQUENCE_SEP);
        }
      }
      return sb.toString();
    } else {
      return obj.toString();
    }
  }

  /**
   * Escape a string so that it'll be parsed as one item, even if it contains
   * SEQUENCE_SEP.
   */
  public static String escape(String s)
  {
    boolean bNeedEscape = s.indexOf(SEQUENCE_SEP) != -1;
    if (bNeedEscape) {
      if (s.length() > 1 && s.startsWith("\"") && s.endsWith("\"")) {
        bNeedEscape = false;
      }
    }
    if (bNeedEscape) {
      return "\"" + s + "\"";
    } else {
      return s;
    }
  }

  public static String toStringFromArray(Object array)
  {
    final StringBuilder sb = new StringBuilder();

    if (array == null) {
      sb.append("null");
    } else {
      for (int i = 0; i < Array.getLength(array); i++) {
        final String s = escape(Array.get(array, i).toString());

        sb.append(s);

        if (i < Array.getLength(array) - 1) {
          sb.append(SEQUENCE_SEP);
        }
      }
    }
    return sb.toString();
  }

  public static String toString(Object[] values)
  {
    final StringBuilder sb = new StringBuilder();

    if (values == null) {
      sb.append("null");
    } else {
      for (int i = 0; i < values.length; i++) {
        final String s = escape(values[i].toString());

        sb.append(s);

        if (i < values.length - 1) {
          sb.append(SEQUENCE_SEP);
        }
      }
    }
    return sb.toString();
  }

  public static String toString(Vector<?> values)
  {
    final StringBuilder sb = new StringBuilder();

    if (values == null) {
      sb.append("null");
    } else {
      for (int i = 0; i < values.size(); i++) {
        sb.append(values.elementAt(i));
        if (i < values.size() - 1) {
          sb.append(SEQUENCE_SEP);
        }
      }
    }
    return sb.toString();
  }

  public int compareTo(AD o)
  {
    return id.compareTo(o.id);
  }

  @Override
  public int hashCode()
  {
    return id.hashCode();
  }

  @Override
  public boolean equals(Object other)
  {
    if (other == null || !(other instanceof AD)) {
      return false;
    }

    return id.equals(((AD) other).id);
  }

}
